/*
** DumpCtl.c
** Segment dump control used to display a buffer in hexadecimal and in ASCII/ATASCII.
*/

#include <windows.h>
#include <windowsx.h>
#include <memory.h>
#include <string.h>
#include "dumpctl.h"

static void DumpRefresh(HWND hWnd);

/*
** structure where buffer size and graphic mode characteristics are saved.
*/
typedef struct DumpStruct
               {
               BYTE      *pBuf;         /* buffer address */
               BYTE      *pType;        /* buffer address */
               WORD      wSize;         /* size (in bytes) of the buffer to display */
               WORD      wStart;        /* start address of the buffer to display */
               WORD      wScroll;       /* first line display (zero based) */
               WORD      wMaxScroll;    /* maximum scroll range */
               WORD      wMaxLines;     /* number of lines displayed in the window */
               HFONT     hFont;         /* font used to display dump */
               WORD      wFontWidth;    /* font width in pixels */
               WORD      wFontHeight;   /* font height in pixels */
               WORD      wBeginLine;    /* first line of selection */
               WORD      wBeginRow;     /* first row of selection */
               WORD      wEndLine;      /* last line of selection */
               WORD      wEndRow;       /* last row of selection */
               BOOL      bInternal;     /* internal display (use ANTIC code) */
               } DumpStruct;

/*
** Are mouse event captured ?
*/
BOOL bDumpCapture = FALSE;

/*
** colors used for different byte type.
*/
static COLORREF dwDumpColor[DUMP_TYPE_NUMBER] =
               {
               RGB(0, 0, 0),
               RGB(192, 192, 192),
               RGB(128, 128, 128),
               RGB(128, 0, 0),
               RGB(128, 0, 128),
               RGB(0, 0, 128),
               RGB(255, 127, 0),
               RGB(0, 127, 255),
               RGB(0, 128, 0),
               RGB(255, 128, 255)
               };

/*
** WM_DUMP_SET_SCROLL message
** set first line to display.
** wScroll is a line number (8 bytes displayed per line).
** if bRefresh is TRUE, control is redrawn.
*/
static void DumpSetScroll(HWND hWnd, WORD wScroll, BOOL bRefresh)
{
     if (! (GetWindowLong(hWnd, GWL_STYLE) & WS_VSCROLL))
          return;
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wScroll), wScroll);
     SetScrollPos(hWnd, SB_VERT, wScroll, TRUE);
     if (bRefresh)
          DumpRefresh(hWnd);
}

/*
** WM_DUMP_SET_BEGIN_SELECTION message
** wBegin is the first byte selected. It is converted in line/row.
** if bRefresh is TRUE, control is redrawn.
*/
static void DumpSetBeginSelection(HWND hWnd, WORD wBegin, BOOL bRefresh)
{
WORD wBeginLine;
WORD wBeginRow;

     if (wBegin == DUMP_NO_SELECTION)
          wBeginLine = wBeginRow = DUMP_NO_SELECTION;
     else {
          wBeginLine = wBegin / DUMP_NB_BYTES_PER_LINE;
          wBeginRow = wBegin % DUMP_NB_BYTES_PER_LINE;
          }
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wBeginLine), wBeginLine);
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wBeginRow), wBeginRow);
     if (bRefresh)
          DumpRefresh(hWnd);
}

/*
** WM_DUMP_SET_END_SELECTION message
** wEnd is the last byte selected. It is converted in line/row.
** if bRefresh is TRUE, control is redrawn.
*/
static void DumpSetEndSelection(HWND hWnd, WORD wEnd, BOOL bRefresh)
{
WORD wEndLine;
WORD wEndRow;

     if (wEnd == DUMP_NO_SELECTION)
          wEndLine = wEndRow = DUMP_NO_SELECTION;
     else {
          wEndLine = wEnd / DUMP_NB_BYTES_PER_LINE;
          wEndRow = wEnd % DUMP_NB_BYTES_PER_LINE;
          }
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wEndLine), wEndLine);
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wEndRow), wEndRow);
     if (bRefresh)
          DumpRefresh(hWnd);
}

/*
** WM_DUMP_GET_BEGIN_SELECTION message
** returns the first byte selected.
*/
static long DumpGetBeginSelection(HWND hWnd)
{
WORD wBeginLine;
WORD wBeginRow;

     wBeginLine = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wBeginLine));
     wBeginRow = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wBeginRow));
     if (wBeginLine == DUMP_NO_SELECTION)
          return DUMP_NO_SELECTION;
     else return (long) (DWORD) (wBeginLine * DUMP_NB_BYTES_PER_LINE + wBeginRow);
}

/*
** WM_DUMP_GET_END_SELECTION message
** returns the first byte selected.
*/
static long DumpGetEndSelection(HWND hWnd)
{
WORD wEndLine;
WORD wEndRow;

     wEndLine = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wEndLine));
     wEndRow = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wEndRow));
     if (wEndLine == DUMP_NO_SELECTION)
          return DUMP_NO_SELECTION;
     else return (long) (DWORD) (wEndLine * DUMP_NB_BYTES_PER_LINE + wEndRow);
}

/*
** Initialize scroll bar position and range.
*/
static void DumpInitScroll(HWND hWnd)
{
WORD wSize;
WORD wFontHeight;
WORD wNbLines;
WORD wMaxLines;
WORD wMaxScroll;

     if (! (GetWindowLong(hWnd, GWL_STYLE) & WS_VSCROLL))
          {
          SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wMaxScroll), 0);
          return;
          }
     wSize = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wSize));
     wFontHeight = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wFontHeight));
     wMaxLines = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wMaxLines));
     wNbLines = (wSize + DUMP_NB_BYTES_PER_LINE - 1) / DUMP_NB_BYTES_PER_LINE;
     if (wNbLines <= wMaxLines)
          wMaxScroll = 0;
     else wMaxScroll = wNbLines - wMaxLines;
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wMaxScroll), wMaxScroll);
     SetScrollRange(hWnd, SB_VERT, 0, (wMaxScroll ? wMaxScroll : 1), FALSE);
     DumpSetScroll(hWnd, 0, FALSE);
}

/*
** set size of buffer to display.
** wSize is the number of bytes of the buffer.
** This function does not redraw control.
*/
static void DumpSetSize(HWND hWnd, WORD wSize)
{
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wSize), wSize);
     DumpInitScroll(hWnd);
}

/*
** WM_DUMP_SET_BUFFER message
** set buffer size and pointer
** This function does not redraw control.
*/
static void DumpSetBuffer(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
     SetWindowLong(hWnd, FIELDOFFSET(DumpStruct, pBuf), lParam);
     DumpSetSize(hWnd, wParam);
}

/*
** WM_DUMP_SET_TYPE_BUFFER message
** set type buffer start and pointer to type buffer
** Note: the 2 buffers must have the same size given in WM_DUMP_SET_BUFFER.
** This function does not redraw control.
*/
static void DumpSetTypeBuffer(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wStart), wParam);
     SetWindowLong(hWnd, FIELDOFFSET(DumpStruct, pType), lParam);
}

/*
** WM_DUMP_SET_INTERNAL message
** bInternal is TRUE if we want to display bytes with ANTIC internal codes.
** if bRefresh is TRUE, control is redrawn.
*/
static void DumpSetInternal(HWND hWnd, BOOL bInternal, BOOL bRefresh)
{
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, bInternal), bInternal);
     if (bRefresh)
          DumpRefresh(hWnd);
}

/*
** WM_SETFONT message
** set font to use.
** if bRefresh is TRUE, control is redrawn.
*/
static void DumpSetFont(HWND hWnd, HFONT hFont, BOOL bRefresh)
{
HDC hDC;
TEXTMETRIC tm;
HFONT hOldFont;
WORD wMaxLines;
RECT rc;

     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, hFont), hFont);
     if (hDC = GetDC(hWnd))
          {
          if (hFont)
               hOldFont = SelectObject(hDC, hFont);
          GetTextMetrics(hDC, &tm);
          if (hFont)
               SelectObject(hDC, hOldFont);
          ReleaseDC(hWnd, hDC);
          }
     else {
          tm.tmAveCharWidth = 8;
          tm.tmHeight = 12;
          }
     GetClientRect(hWnd, &rc);
     wMaxLines = rc.bottom / tm.tmHeight;
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wFontWidth), tm.tmAveCharWidth);
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wFontHeight), tm.tmHeight);
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wMaxLines), wMaxLines);
     DumpInitScroll(hWnd);
     if (bRefresh)
          DumpRefresh(hWnd);
}

/*
** WM_SIZE message
** Determine new number of lines in the window.
*/
static long DumpSize(HWND hWnd)
{
WORD wFontHeight;
WORD wMaxLines;
RECT rc;

     wFontHeight = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wFontHeight));
     GetClientRect(hWnd, &rc);
     wMaxLines = rc.bottom / wFontHeight;
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wMaxLines), wMaxLines);
     DumpInitScroll(hWnd);
     return NULL;
}

/*
** WM_CREATE message
** Initialization of the window private structure in the extra bytes.
** The default setting is no buffer (0 byte long) with system font.
*/
static long DumpCreate(HWND hWnd)
{
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wBeginLine), DUMP_NO_SELECTION);
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wBeginRow), DUMP_NO_SELECTION);
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wEndLine), DUMP_NO_SELECTION);
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wEndRow), DUMP_NO_SELECTION);
     DumpSetFont(hWnd, 0, FALSE);
     DumpSetBuffer(hWnd, 0, NULL);
     DumpSetTypeBuffer(hWnd, 0, NULL);
     DumpSetScroll(hWnd, 0, FALSE);
     DumpSetInternal(hWnd, 0, FALSE);
     DumpSize(hWnd);
     return 0;
}

/*
** print only one line.
*/
static void DumpPrintLine(HWND hWnd, HDC hDC, WORD wLine, WORD wNbLines)
{
char szBuf[40];
WORD wFontWidth;
WORD wFontHeight;
WORD wScroll;
WORD wHeight;
WORD wStart;
WORD wSize;
WORD wNbRows;
WORD wOffset;
WORD wBeginLine;
WORD wBeginRow;
WORD wEndLine;
WORD wEndRow;
WORD wFirstLineSelected;
WORD wLastLineSelected;
WORD wFirstRowSelected;
WORD wLastRowSelected;
WORD wRow;
int nX, nY, nW, nH;
int nX2, nW2;
BYTE *pBuf;
BYTE *pType;
BYTE cChar;
BYTE cType, cOldType;
BOOL bInternal;
BOOL bSelect;

     /*
     ** Check if we have a buffer.
     */
     if ((pBuf = (BYTE *) (DWORD) GetWindowLong(hWnd, FIELDOFFSET(DumpStruct, pBuf))) == NULL)
          return;

     /*
     ** get graphic characteristics from window extra bytes.
     */
     pType = (BYTE *) (DWORD) GetWindowLong(hWnd, FIELDOFFSET(DumpStruct, pType));
     wFontWidth = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wFontWidth));
     wFontHeight = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wFontHeight));
     wScroll = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wScroll));
     wStart = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wStart));
     wSize = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wSize));
     wBeginLine = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wBeginLine));
     wBeginRow = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wBeginRow));
     wEndLine = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wEndLine));
     wEndRow = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wEndRow));
     bInternal = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, bInternal));

     /*
     ** draw start address
     */
     wHeight = (wLine - wScroll) * wFontHeight;
     wsprintf(szBuf, "%04X\x7C", wStart + (wLine * DUMP_NB_BYTES_PER_LINE));
     SetTextColor(hDC, RGB(0, 0, 0));
     TextOut(hDC, 1, wHeight, szBuf, strlen(szBuf));
     if (wLine == wNbLines - 1)
          wNbRows = ((wSize - 1) % DUMP_NB_BYTES_PER_LINE) + 1;
     else wNbRows = DUMP_NB_BYTES_PER_LINE;
     bSelect = FALSE;

     /*
     ** Is there a selection
     */
     if (wBeginLine != DUMP_NO_SELECTION)
          {
          if (wBeginLine > wEndLine)
               {
               wFirstLineSelected = wEndLine;
               wLastLineSelected = wBeginLine;
               wFirstRowSelected = wEndRow;
               wLastRowSelected = wBeginRow;
               }
          else if (wBeginLine < wEndLine)
               {
               wFirstLineSelected = wBeginLine;
               wLastLineSelected = wEndLine;
               wFirstRowSelected = wBeginRow;
               wLastRowSelected = wEndRow;
               }
          else {
               wFirstLineSelected = wLastLineSelected = wBeginLine;
               if (wBeginRow > wEndRow)
                    {
                    wFirstRowSelected = wEndRow;
                    wLastRowSelected = wBeginRow;
                    }
               else {
                    wFirstRowSelected = wBeginRow;
                    wLastRowSelected = wEndRow;
                    }
               }
          if (wLine >= wFirstLineSelected && wLine <= wLastLineSelected)
               {
               bSelect = TRUE;
               if (wLine == wFirstLineSelected)
                    wRow = wFirstRowSelected;
               else wRow = 0;
               nX = (int) (wFontWidth * ((wRow * 3) + 5)) + 1;
               nX2 = (int) (wFontWidth * ((DUMP_NB_BYTES_PER_LINE * 3) + wRow + 5)) + 1;
               nY = (int) wHeight;
               if (wLine == wLastLineSelected)
                    wRow = wLastRowSelected + 1 - wRow;
               else wRow = DUMP_NB_BYTES_PER_LINE - wRow;
               nW = (int) (wFontWidth * wRow * 3) - wFontWidth;
               nW2 = (int) (wFontWidth * wRow);
               nH = wFontHeight;
               }
          }

     /*
     ** draw hexadecimal and bytes.
     */
     szBuf[0] = 0;
     cOldType = 0xFF;
     for (wRow = 0; wRow < wNbRows; wRow++)
          {
          wOffset = (wLine * DUMP_NB_BYTES_PER_LINE) + wRow;
          cChar = pBuf[wOffset];
          if (pType)
               {
               cType = pType[wOffset];
               if (wOffset)
                    {
                    if ((cOldType != DUMP_TYPE_LOBYTE) && (cOldType != DUMP_TYPE_HIBYTE)
                      && ((pType[wOffset - 1] == DUMP_TYPE_LOBYTE) || (pType[wOffset - 1] == DUMP_TYPE_HIBYTE)))
                         cType = pType[wOffset - 1];
                    else if ((cType == DUMP_TYPE_LOBYTE) || (cType == DUMP_TYPE_HIBYTE))
                         cType = DUMP_TYPE_CODE;
                    }
               else if ((cType == DUMP_TYPE_LOBYTE) || (cType == DUMP_TYPE_HIBYTE))
                    cType = DUMP_TYPE_CODE;
               }
          else cType = DUMP_TYPE_CODE;
          if (cType >= DUMP_TYPE_NUMBER)
               cType = DUMP_TYPE_CODE;
          cOldType = cType;
          wsprintf(szBuf, "%02X ", cChar);
          SetTextColor(hDC, dwDumpColor[cType]);
          TextOut(hDC, (wFontWidth * 5) + (wFontWidth * 3 * wRow) + 1, wHeight, szBuf, strlen(szBuf));
          if (bInternal)
               {
               if (cChar < 64)
                    cChar += 32;
               else if (cChar < 96)
                    cChar -= 64;
               else if ((cChar >= 128) && (cChar < 128 + 64))
                    cChar += 32;
               else if ((cChar >= 128 + 64) && (cChar < 128 + 96))
                    cChar -= 64;
               }
          TextOut(hDC, (wFontWidth * ((DUMP_NB_BYTES_PER_LINE * 3) + wRow + 5)) + 1, wHeight, &cChar, 1);
          SetTextColor(hDC, RGB(0, 0, 0));
          }

     /*
     ** invert line if we have a selection.
     */
     if (bSelect)
          {
          PatBlt(hDC, nX, nY, nW, nH, DSTINVERT);
          PatBlt(hDC, nX2, nY, nW2, nH, DSTINVERT);
          }

     /*
     ** fill the rest of line if there is no more byte to display
     */
     for (; wRow < DUMP_NB_BYTES_PER_LINE; wRow++)
          {
          cChar = ' ';
          strcpy(szBuf, "   ");
          TextOut(hDC, (wFontWidth * ((wRow * 3) + 5)) + 1, wHeight, szBuf, strlen(szBuf));
          TextOut(hDC, (wFontWidth * ((DUMP_NB_BYTES_PER_LINE * 3) + wRow + 5)) + 1, wHeight, &cChar, 1);
          }
     TextOut(hDC, (wFontWidth * ((DUMP_NB_BYTES_PER_LINE * 3) + 4)) + 1, wHeight, "\x7C", 1);
}

/*
** draw one line
*/
static void DumpPrintOne(HWND hWnd, HDC hDC, WORD wLine)
{
HFONT hFont;
HFONT hOldFont;
WORD wSize;
WORD wNbLines;

     /*
     ** Check if we have a buffer.
     */
     if (GetWindowLong(hWnd, FIELDOFFSET(DumpStruct, pBuf)) == NULL)
          return;

     /*
     ** select font and print all lines
     */
     if (hFont = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, hFont)))
          hOldFont = SelectObject(hDC, hFont);
     wSize = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wSize));
     wNbLines = (wSize + DUMP_NB_BYTES_PER_LINE - 1) / DUMP_NB_BYTES_PER_LINE;
     DumpPrintLine(hWnd, hDC, wLine, wNbLines);
     if (hFont)
          SelectObject(hDC, hOldFont);
}

/*
** repaint all control.
*/
static void DumpPaintAll(HWND hWnd, HDC hDC)
{
HFONT hFont;
HFONT hOldFont;
WORD wSize;
WORD wScroll;
WORD wLine;
WORD wNbLines;
WORD wMaxLine;
WORD wMaxLineInWindow;

     /*
     ** Check if we have a buffer.
     */
     if (GetWindowLong(hWnd, FIELDOFFSET(DumpStruct, pBuf)) == NULL)
          return;

     /*
     ** select font and print all lines
     */
     if (hFont = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, hFont)))
          hOldFont = SelectObject(hDC, hFont);
     wMaxLineInWindow = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wMaxLines));
     wScroll = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wScroll));
     wSize = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wSize));
     wNbLines = (wSize + DUMP_NB_BYTES_PER_LINE - 1) / DUMP_NB_BYTES_PER_LINE;
     wMaxLine = min(wNbLines, wScroll + wMaxLineInWindow + 1);
     for (wLine = wScroll; wLine < wMaxLine; wLine++)
          DumpPrintLine(hWnd, hDC, wLine, wNbLines);
     if (hFont)
          SelectObject(hDC, hOldFont);
}

/*
** WM_PAINT message
** redraw the buffer
*/
static long DumpPaint(HWND hWnd)
{
PAINTSTRUCT ps;
HDC hDC;

     hDC = BeginPaint(hWnd, &ps);
     DumpPaintAll(hWnd, hDC);
     EndPaint(hWnd, &ps);
     return NULL;
}

/*
** WM_DUMP_REFRESH message
** refresh window
*/
static void DumpRefresh(HWND hWnd)
{
HDC hDC;

     hDC = GetDC(hWnd);
     if (hDC)
          {
          DumpPaintAll(hWnd, hDC);
          ReleaseDC(hWnd, hDC);
          }
}

/*
** WM_DUMP_SEEK_SELECTION message
** set first line to begining of selection
*/
static void DumpSeekSelection(HWND hWnd)
{
WORD wBeginLine;
WORD wScroll;
WORD wMaxScroll;

     /*
     ** if no selection, nothing to do.
     */
     wBeginLine = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wBeginLine));
     if (wBeginLine == DUMP_NO_SELECTION)
          {
          DumpRefresh(hWnd);
          return;
          }

     /*
     ** We must scroll but we have to check if the selection can be drawn on the first line.
     */
     wMaxScroll = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wMaxScroll));
     wScroll = min(wBeginLine, wMaxScroll);

     /*
     ** change scroll position.
     */
     DumpSetScroll(hWnd, wScroll, TRUE);
}

/*
** scroll one line up (with BitBlt)
*/
static BOOL DumpScrollUp(HWND hWnd, WORD wScroll, WORD wFontHeight, RECT *rc)
{
HDC hDC;

     hDC = GetDC(hWnd);
     if (hDC)
          {
          wFontHeight = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wFontHeight));
          BitBlt(hDC, rc->left, rc->top + wFontHeight, rc->right, rc->bottom - wFontHeight, hDC, 0, 0, SRCCOPY);
          DumpPrintOne(hWnd, hDC, wScroll);
          ReleaseDC(hWnd, hDC);
          }
     return (BOOL) hDC;
}

/*
** scroll one line down (with BitBlt)
*/
static BOOL DumpScrollDown(HWND hWnd, WORD wScroll, WORD wFontHeight, RECT *rc, WORD wNbLines)
{
HDC hDC;

     hDC = GetDC(hWnd);
     if (hDC)
          {
          wFontHeight = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wFontHeight));
          BitBlt(hDC, rc->left, rc->top, rc->right, rc->bottom - wFontHeight, hDC, 0, wFontHeight, SRCCOPY);
          DumpPrintOne(hWnd, hDC, wScroll + wNbLines - 1);
          ReleaseDC(hWnd, hDC);
          }
     return (BOOL) hDC;
}

/*
** WM_VSCROLL message
*/
static long DumpVScroll(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
WORD wOldScroll;
WORD wScroll;
WORD wMaxScroll;
WORD wFontHeight;
WORD wNbLines;
RECT rc;

     wOldScroll = wScroll = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wScroll));
     wMaxScroll = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wMaxScroll));
     wFontHeight = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wFontHeight));
     GetClientRect(hWnd, &rc);
     wNbLines = rc.bottom / wFontHeight;
     switch (wParam)
          {
          case SB_TOP:
               if (wScroll)
                    {
                    wScroll = 0;
                    DumpSetScroll(hWnd, wScroll, TRUE);
                    }
               break;

          case SB_BOTTOM:
               if (wScroll != wMaxScroll)
                    {
                    wScroll = wMaxScroll;
                    DumpSetScroll(hWnd, wScroll, TRUE);
                    }
               break;

          case SB_LINEUP:
               if (wScroll)
                    {
                    wScroll--;
                    DumpSetScroll(hWnd, wScroll, FALSE);
                    if (! DumpScrollUp(hWnd, wScroll, wFontHeight, &rc))
                         DumpRefresh(hWnd);
                    }
               break;

          case SB_LINEDOWN:
               if (wScroll != wMaxScroll)
                    {
                    wScroll++;
                    DumpSetScroll(hWnd, wScroll, FALSE);
                    if (! DumpScrollDown(hWnd, wScroll, wFontHeight, &rc, wNbLines))
                         DumpRefresh(hWnd);
                    }
               break;

          case SB_PAGEUP:
               if (wScroll)
                    {
                    wScroll -= min(wScroll, wNbLines);
                    DumpSetScroll(hWnd, wScroll, TRUE);
                    }
               break;

          case SB_PAGEDOWN:
               if (wScroll != wMaxScroll)
                    {
                    wScroll += min(wMaxScroll - wScroll, wNbLines);
                    DumpSetScroll(hWnd, wScroll, TRUE);
                    }
               break;

          case SB_THUMBPOSITION:
          case SB_THUMBTRACK:
               if ((wScroll != LOWORD(lParam)) && (LOWORD(lParam) <= wMaxScroll))
                    {
                    wScroll = LOWORD(lParam);
                    DumpSetScroll(hWnd, wScroll, TRUE);
                    }
               break;
          }
     if (wOldScroll != wScroll)
          SendMessage(GetParent(hWnd), WM_COMMAND, GetDlgCtrlID(hWnd), MAKELONG(hWnd, DUMP_SCROLL_CHANGED));
     return NULL;
}

/*
** WM_LBUTTONDOWN message
*/
static long DumpLButtonDown(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
WORD wFontWidth;
WORD wFontHeight;
WORD wScroll;
WORD wBeginLine;
WORD wBeginRow;
WORD wEndLine;
WORD wEndRow;

     /*
     ** Check if we have a buffer.
     */
     if (GetWindowLong(hWnd, FIELDOFFSET(DumpStruct, pBuf)) == NULL)
          return NULL;

     /*
     ** Capture mouse input.
     */
     if (bDumpCapture == FALSE)
          SetCapture(hWnd);
     bDumpCapture = TRUE;

     /*
     ** Get first line of selection
     */
     wScroll = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wScroll));
     wFontWidth = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wFontWidth));
     wFontHeight = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wFontHeight));
     wBeginLine = wScroll + (HIWORD(lParam) / wFontHeight);
     if (LOWORD(lParam) < (5 * wFontWidth))
          wBeginRow = 0;
     else {
          if (LOWORD(lParam) > (29 * wFontWidth))
               wBeginRow = (LOWORD(lParam) - 1 - (29 * wFontWidth)) / wFontWidth;
          else wBeginRow = (LOWORD(lParam) - 1 - (4 * wFontWidth) - (wFontWidth / 2)) / (3 * wFontWidth);
          if (wBeginRow > DUMP_NB_BYTES_PER_LINE - 1)
               wBeginRow = DUMP_NB_BYTES_PER_LINE - 1;
          }

     /*
     ** Set end of selection to beginning of selection
     */
     wEndLine = wBeginLine;
     wEndRow = wBeginRow;

     /*
     ** Save new selection and redraw control.
     */
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wBeginLine), wBeginLine);
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wBeginRow), wBeginRow);
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wEndLine), wEndLine);
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wEndRow), wEndRow);
     DumpRefresh(hWnd);
     return NULL;
}

/*
** Set end of selection (scroll window if selection extends outside window).
*/
static void DumpSetEndOfSelection(HWND hWnd, LPARAM lParam)
{
WORD wFontWidth;
WORD wFontHeight;
WORD wScroll;
WORD wEndLine;
WORD wEndRow;
WORD wMaxLines;
RECT rc;

     /*
     ** determine new end of selection
     */
     wScroll = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wScroll));
     wFontWidth = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wFontWidth));
     wFontHeight = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wFontHeight));
     wMaxLines = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wMaxLines));
     GetClientRect(hWnd, &rc);
     if ((int) HIWORD(lParam) < 0)
          {
          DumpVScroll(hWnd, SB_LINEUP, 0);
          wScroll = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wScroll));
          wEndLine = wScroll;
          }
     else if (HIWORD(lParam) > (WORD) rc.bottom)
          {
          DumpVScroll(hWnd, SB_LINEDOWN, 0);
          wScroll = GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wScroll));
          wEndLine = wScroll + wMaxLines - 1;
          }
     else wEndLine = wScroll + (HIWORD(lParam) / wFontHeight);
     if (LOWORD(lParam) < (5 * wFontWidth))
          wEndRow = 0;
     else {
          if (LOWORD(lParam) > (29 * wFontWidth))
               wEndRow = (LOWORD(lParam) - 1 - (29 * wFontWidth)) / wFontWidth;
          else wEndRow = (LOWORD(lParam) - 1 - (4 * wFontWidth) - (wFontWidth / 2)) / (3 * wFontWidth);
          if (wEndRow > DUMP_NB_BYTES_PER_LINE - 1)
               wEndRow = DUMP_NB_BYTES_PER_LINE - 1;
          }

     /*
     ** Save new end of selection and redraw control.
     */
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wEndLine), wEndLine);
     SetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wEndRow), wEndRow);
     DumpRefresh(hWnd);
}

/*
** WM_MOUSEMOVE message
*/
static long DumpMouseMove(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
     /*
     ** Check if we have a buffer.
     */
     if (GetWindowLong(hWnd, FIELDOFFSET(DumpStruct, pBuf)) == NULL)
          return NULL;

     /*
     ** check we are in selection mode.
     */
     if (! bDumpCapture)
          return NULL;

     /*
     ** determine new end of selection
     */
     DumpSetEndOfSelection(hWnd, lParam);
     return NULL;
}

/*
** WM_LBUTTONUP message
*/
static long DumpLButtonUp(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
     /*
     ** Check if we have a buffer.
     */
     if (GetWindowLong(hWnd, FIELDOFFSET(DumpStruct, pBuf)) == NULL)
          return NULL;

     /*
     ** Release mouse input.
     */
     if (bDumpCapture)
          ReleaseCapture();
     bDumpCapture = FALSE;

     /*
     ** determine new end of selection
     */
     DumpSetEndOfSelection(hWnd, lParam);

     /*
     ** notify parent of the selection change.
     */
     SendMessage(GetParent(hWnd), WM_COMMAND, GetDlgCtrlID(hWnd), MAKELONG(hWnd, DUMP_SELECTION_CHANGED));
     return NULL;
}

/*
** WM_RBUTTONDOWN message
*/
static long DumpRButtonDown(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
     /*
     ** Check if we have a buffer.
     */
     if (GetWindowLong(hWnd, FIELDOFFSET(DumpStruct, pBuf)) == NULL)
          return NULL;

     /*
     ** notify parent of the right button click.
     */
     SendMessage(GetParent(hWnd), WM_COMMAND, GetDlgCtrlID(hWnd), MAKELONG(hWnd, DUMP_RBUTTONDOWN));
     return NULL;
}

/*
** Dump window proc.
*/
long CALLBACK __export DumpWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     switch (message)
          {
          case WM_DUMP_SET_BUFFER:
               DumpSetBuffer(hWnd, wParam, lParam);
               break;

          case WM_DUMP_SET_TYPE_BUFFER:
               DumpSetTypeBuffer(hWnd, wParam, lParam);
               break;

          case WM_DUMP_SET_SCROLL:
               DumpSetScroll(hWnd, wParam, (BOOL) lParam);
               break;

          case WM_DUMP_GET_SCROLL:
               return (long) (DWORD) GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, wScroll));

          case WM_DUMP_SET_INTERNAL:
               DumpSetInternal(hWnd, wParam, (BOOL) lParam);
               break;

          case WM_DUMP_GET_INTERNAL:
               return (long) (DWORD) GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, bInternal));

          case WM_DUMP_SET_BEGIN_SELECTION:
               DumpSetBeginSelection(hWnd, wParam, (BOOL) lParam);
               break;

          case WM_DUMP_GET_BEGIN_SELECTION:
               return DumpGetBeginSelection(hWnd);

          case WM_DUMP_SET_END_SELECTION:
               DumpSetEndSelection(hWnd, wParam, (BOOL) lParam);
               break;

          case WM_DUMP_GET_END_SELECTION:
               return DumpGetEndSelection(hWnd);

          case WM_DUMP_SEEK_SELECTION:
               DumpSeekSelection(hWnd);
               break;

          case WM_DUMP_REFRESH:
               DumpRefresh(hWnd);
               break;

          case WM_SETFONT:
               DumpSetFont(hWnd, (HFONT) wParam, (BOOL) lParam);
               break;

          case WM_GETFONT:
               return (long) (DWORD) GetWindowWord(hWnd, FIELDOFFSET(DumpStruct, hFont));

          case WM_CREATE:
               return DumpCreate(hWnd);

          case WM_SIZE:
               return DumpSize(hWnd);

          case WM_PAINT:
               return DumpPaint(hWnd);

          case WM_VSCROLL:
               return DumpVScroll(hWnd, wParam, lParam);

          case WM_LBUTTONDOWN:
               return DumpLButtonDown(hWnd, wParam, lParam);

          case WM_MOUSEMOVE:
               return DumpMouseMove(hWnd, wParam, lParam);

          case WM_LBUTTONUP:
               return DumpLButtonUp(hWnd, wParam, lParam);

          case WM_RBUTTONDOWN:
               return DumpRButtonDown(hWnd, wParam, lParam);

          default:
               return DefWindowProc(hWnd, message, wParam, lParam);
          }
     return NULL;
}

BOOL CALLBACK __export DumpInit(HINSTANCE hInst)
{
WNDCLASS wc;

     /*
     ** Fill in window class structure with parameters that describe the
     ** dump window.
     */
     wc.style = NULL;
     wc.lpfnWndProc = DumpWndProc;
     wc.cbClsExtra = 0;
     wc.cbWndExtra = sizeof(DumpStruct);
     wc.hInstance = hInst;
     wc.hIcon = NULL;
     wc.hCursor = LoadCursor(NULL, IDC_ARROW);
     wc.hbrBackground = GetStockObject(WHITE_BRUSH);
     wc.lpszMenuName = NULL;
     wc.lpszClassName = "DumpCtrlClass";

     /*
     ** Register the window class.
     */
     return RegisterClass(&wc);
}

/*
** DLL entry point
*/
BOOL CALLBACK LibMain(HINSTANCE hinst, UINT wDS, UINT cbHeap, DWORD unused)
{
     return TRUE;
}

/*
** DLL exit point
*/
int FAR PASCAL _WEP(int unused)
{
     return TRUE;
}
